/*
* Copyright (C) 2012 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.auto.value;
import static com.google.common.truth.Truth.assertThat;
import static com.google.testing.compile.CompilationSubject.assertThat;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.testing.EqualsTester;
import com.google.testing.compile.Compilation;
import com.google.testing.compile.Compiler;
import com.google.testing.compile.JavaFileObjects;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for constructs new in Java 8, such as type annotations.
*
* @author Till Brychcy
* @author emcmanus@google.com (Éamonn McManus)
*/
@RunWith(JUnit4.class)
public class AutoValueJava8Test {
private static boolean javacHandlesTypeAnnotationsCorrectly;
// This is appalling. Some versions of javac do not correctly report annotations on type uses in
// certain cases, for example on type variables or arrays. Since some of the tests here are for
// exactly that, we compile a test program with a test annotation processor to see whether we
// might be in the presence of such a javac, and if so we skip the tests that would fail because
// of the bug. This isn't completely sound because we can't be entirely sure that the javac that
// Compiler.javac() finds is the same as the javac that was used to build this test (and therefore
// run AutoValueProcessor), but it's better than just ignoring the tests outright.
@BeforeClass
public static void setUpClass() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"Test",
"import java.lang.annotation.ElementType;",
"import java.lang.annotation.Retention;",
"import java.lang.annotation.RetentionPolicy;",
"import java.lang.annotation.Target;",
"public abstract class Test<T> {",
" @Retention(RetentionPolicy.RUNTIME)",
" @Target(ElementType.TYPE_USE)",
" public @interface Nullable {}",
"",
" public abstract @Nullable T t();",
"}");
Compilation compilation =
Compiler.javac().withProcessors(new BugTestProcessor()).compile(javaFileObject);
if (compilation.errors().isEmpty()) {
javacHandlesTypeAnnotationsCorrectly = true;
} else {
assertThat(compilation).hadErrorCount(1);
assertThat(compilation).hadErrorContaining(JAVAC_HAS_BUG_ERROR);
}
}
private static final String JAVAC_HAS_BUG_ERROR = "javac has the type-annotation bug";
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
private static class BugTestProcessor extends AbstractProcessor {
@Override public boolean process(
Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
test();
}
return false;
}
private void test() {
TypeElement test = processingEnv.getElementUtils().getTypeElement("Test");
List<ExecutableElement> methods = ElementFilter.methodsIn(test.getEnclosedElements());
ExecutableElement t = Iterables.getOnlyElement(methods);
assertThat(t.getSimpleName().toString()).isEqualTo("t");
List<? extends AnnotationMirror> typeAnnotations = t.getReturnType().getAnnotationMirrors();
if (typeAnnotations.isEmpty()) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, JAVAC_HAS_BUG_ERROR);
return;
}
AnnotationMirror typeAnnotation = Iterables.getOnlyElement(typeAnnotations);
assertThat(typeAnnotation.getAnnotationType().toString()).contains("Nullable");
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface Nullable {}
@AutoValue
abstract static class NullableProperties {
abstract @Nullable String nullableString();
abstract int randomInt();
static NullableProperties create(@Nullable String nullableString, int randomInt) {
return new AutoValue_AutoValueJava8Test_NullableProperties(nullableString, randomInt);
}
}
@Test
public void testNullablePropertiesCanBeNull() {
NullableProperties instance = NullableProperties.create(null, 23);
assertThat(instance.nullableString()).isNull();
assertThat(instance.randomInt()).isEqualTo(23);
assertThat(instance.toString())
.isEqualTo("NullableProperties{nullableString=null, randomInt=23}");
}
@AutoAnnotation
static Nullable nullable() {
return new AutoAnnotation_AutoValueJava8Test_nullable();
}
@Test
public void testNullablePropertyConstructorParameterIsNullable() throws NoSuchMethodException {
Constructor<?> constructor =
AutoValue_AutoValueJava8Test_NullableProperties.class.getDeclaredConstructor(
String.class, int.class);
assertThat(constructor.getAnnotatedParameterTypes()[0].getAnnotations()).asList()
.contains(nullable());
}
@AutoValue
abstract static class NullableNonNullable {
abstract @Nullable String nullableString();
abstract @Nullable String otherNullableString();
abstract String nonNullableString();
static NullableNonNullable create(
String nullableString, String otherNullableString, String nonNullableString) {
return new AutoValue_AutoValueJava8Test_NullableNonNullable(
nullableString, otherNullableString, nonNullableString);
}
}
@Test
public void testEqualsWithNullable() throws Exception {
NullableNonNullable everythingNull =
NullableNonNullable.create(null, null, "nonNullableString");
NullableNonNullable somethingNull =
NullableNonNullable.create(null, "otherNullableString", "nonNullableString");
NullableNonNullable nothingNull =
NullableNonNullable.create("nullableString", "otherNullableString", "nonNullableString");
NullableNonNullable nothingNullAgain =
NullableNonNullable.create("nullableString", "otherNullableString", "nonNullableString");
new EqualsTester()
.addEqualityGroup(everythingNull)
.addEqualityGroup(somethingNull)
.addEqualityGroup(nothingNull, nothingNullAgain)
.testEquals();
}
@AutoValue
abstract static class PrimitiveArrays {
@SuppressWarnings("mutable")
abstract boolean[] booleans();
@SuppressWarnings("mutable")
abstract int @Nullable[] ints();
static PrimitiveArrays create(boolean[] booleans, int[] ints) {
// Real code would likely clone these parameters, but here we want to check that the
// generated constructor rejects a null value for booleans.
return new AutoValue_AutoValueJava8Test_PrimitiveArrays(booleans, ints);
}
}
@Test
public void testPrimitiveArrays() {
PrimitiveArrays object0 = PrimitiveArrays.create(new boolean[0], new int[0]);
boolean[] booleans = {false, true, true, false};
int[] ints = {6, 28, 496, 8128, 33550336};
PrimitiveArrays object1 = PrimitiveArrays.create(booleans.clone(), ints.clone());
PrimitiveArrays object2 = PrimitiveArrays.create(booleans.clone(), ints.clone());
new EqualsTester()
.addEqualityGroup(object1, object2)
.addEqualityGroup(object0)
.testEquals();
// EqualsTester also exercises hashCode(). We clone the arrays above to ensure that using the
// default Object.hashCode() will fail.
String expectedString = "PrimitiveArrays{booleans=" + Arrays.toString(booleans) + ", "
+ "ints=" + Arrays.toString(ints) + "}";
assertThat(object1.toString()).isEqualTo(expectedString);
assertThat(object1.ints()).isSameAs(object1.ints());
}
@Test
public void testNullablePrimitiveArrays() {
assumeTrue(javacHandlesTypeAnnotationsCorrectly);
PrimitiveArrays object0 = PrimitiveArrays.create(new boolean[0], null);
boolean[] booleans = {false, true, true, false};
PrimitiveArrays object1 = PrimitiveArrays.create(booleans.clone(), null);
PrimitiveArrays object2 = PrimitiveArrays.create(booleans.clone(), null);
new EqualsTester()
.addEqualityGroup(object1, object2)
.addEqualityGroup(object0)
.testEquals();
String expectedString = "PrimitiveArrays{booleans=" + Arrays.toString(booleans) + ", "
+ "ints=null}";
assertThat(object1.toString()).isEqualTo(expectedString);
assertThat(object1.booleans()).isSameAs(object1.booleans());
assertThat(object1.booleans()).isEqualTo(booleans);
object1.booleans()[0] ^= true;
assertThat(object1.booleans()).isNotEqualTo(booleans);
}
@Test
public void testNotNullablePrimitiveArrays() {
try {
PrimitiveArrays.create(null, new int[0]);
fail("Construction with null value for non-@Nullable array should have failed");
} catch (NullPointerException e) {
assertThat(e.getMessage()).contains("booleans");
}
}
@AutoValue
public abstract static class NullablePropertyWithBuilder {
public abstract String notNullable();
public abstract @Nullable String nullable();
public static Builder builder() {
return new AutoValue_AutoValueJava8Test_NullablePropertyWithBuilder.Builder();
}
@AutoValue.Builder
public interface Builder {
Builder notNullable(String s);
Builder nullable(@Nullable String s);
NullablePropertyWithBuilder build();
}
}
@Test
public void testOmitNullableWithBuilder() {
NullablePropertyWithBuilder instance1 = NullablePropertyWithBuilder.builder()
.notNullable("hello")
.build();
assertThat(instance1.notNullable()).isEqualTo("hello");
assertThat(instance1.nullable()).isNull();
NullablePropertyWithBuilder instance2 = NullablePropertyWithBuilder.builder()
.notNullable("hello")
.nullable(null)
.build();
assertThat(instance2.notNullable()).isEqualTo("hello");
assertThat(instance2.nullable()).isNull();
assertThat(instance1).isEqualTo(instance2);
NullablePropertyWithBuilder instance3 = NullablePropertyWithBuilder.builder()
.notNullable("hello")
.nullable("world")
.build();
assertThat(instance3.notNullable()).isEqualTo("hello");
assertThat(instance3.nullable()).isEqualTo("world");
try {
NullablePropertyWithBuilder.builder().build();
fail("Expected IllegalStateException for unset non-@Nullable property");
} catch (IllegalStateException e) {
assertThat(e.getMessage()).contains("notNullable");
}
}
@AutoValue
public abstract static class BuilderWithUnprefixedGetters<T extends Comparable<T>> {
public abstract ImmutableList<T> list();
public abstract @Nullable T t();
@SuppressWarnings("mutable")
public abstract int[] ints();
public abstract int noGetter();
public static <T extends Comparable<T>> Builder<T> builder() {
return new AutoValue_AutoValueJava8Test_BuilderWithUnprefixedGetters.Builder<T>();
}
@AutoValue.Builder
public interface Builder<T extends Comparable<T>> {
Builder<T> setList(ImmutableList<T> list);
Builder<T> setT(T t);
Builder<T> setInts(int[] ints);
Builder<T> setNoGetter(int x);
ImmutableList<T> list();
T t();
int[] ints();
BuilderWithUnprefixedGetters<T> build();
}
}
@Test
public void testBuilderWithUnprefixedGetter() {
assumeTrue(javacHandlesTypeAnnotationsCorrectly);
ImmutableList<String> names = ImmutableList.of("fred", "jim");
int[] ints = {6, 28, 496, 8128, 33550336};
int noGetter = -1;
BuilderWithUnprefixedGetters.Builder<String> builder = BuilderWithUnprefixedGetters.builder();
assertThat(builder.t()).isNull();
try {
builder.list();
fail("Attempt to retrieve unset list property should have failed");
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Property \"list\" has not been set");
}
try {
builder.ints();
fail("Attempt to retrieve unset ints property should have failed");
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Property \"ints\" has not been set");
}
builder.setList(names);
assertThat(builder.list()).isSameAs(names);
builder.setInts(ints);
assertThat(builder.ints()).isEqualTo(ints);
// The array is not cloned by the getter, so the client can modify it (but shouldn't).
ints[0] = 0;
assertThat(builder.ints()[0]).isEqualTo(0);
ints[0] = 6;
BuilderWithUnprefixedGetters<String> instance = builder.setNoGetter(noGetter).build();
assertThat(instance.list()).isSameAs(names);
assertThat(instance.t()).isNull();
assertThat(instance.ints()).isEqualTo(ints);
assertThat(instance.noGetter()).isEqualTo(noGetter);
}
@AutoValue
public abstract static class BuilderWithPrefixedGetters<T extends Comparable<T>> {
public abstract ImmutableList<T> getList();
public abstract T getT();
@SuppressWarnings("mutable")
public abstract int @Nullable [] getInts();
public abstract int getNoGetter();
public static <T extends Comparable<T>> Builder<T> builder() {
return new AutoValue_AutoValueJava8Test_BuilderWithPrefixedGetters.Builder<T>();
}
@AutoValue.Builder
public abstract static class Builder<T extends Comparable<T>> {
public abstract Builder<T> setList(ImmutableList<T> list);
public abstract Builder<T> setT(T t);
public abstract Builder<T> setInts(int[] ints);
public abstract Builder<T> setNoGetter(int x);
abstract ImmutableList<T> getList();
abstract T getT();
abstract int[] getInts();
public abstract BuilderWithPrefixedGetters<T> build();
}
}
@Test
public void testBuilderWithPrefixedGetter() {
assumeTrue(javacHandlesTypeAnnotationsCorrectly);
ImmutableList<String> names = ImmutableList.of("fred", "jim");
String name = "sheila";
int noGetter = -1;
BuilderWithPrefixedGetters.Builder<String> builder = BuilderWithPrefixedGetters.builder();
assertThat(builder.getInts()).isNull();
try {
builder.getList();
fail("Attempt to retrieve unset list property should have failed");
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Property \"list\" has not been set");
}
builder.setList(names);
assertThat(builder.getList()).isSameAs(names);
builder.setT(name);
assertThat(builder.getInts()).isNull();
BuilderWithPrefixedGetters<String> instance = builder.setNoGetter(noGetter).build();
assertThat(instance.getList()).isSameAs(names);
assertThat(instance.getT()).isEqualTo(name);
assertThat(instance.getInts()).isNull();
assertThat(instance.getNoGetter()).isEqualTo(noGetter);
}
}